#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use Time::HiRes 'time';
require 'forum_queries.pl';

my $dbh = 0;
$forum::errmsg = '';
$forum::errno = 0; # (< 0) means my own custom error message or something

$forum::t_p = 1000;
$forum::p_p = 1000;
$forum::u_p = 1000;

$forum::db = '__hidden__';
$forum::host = '__hidden__';
$forum::user = '__hidden__';
$forum::pass = '__hidden__';
$forum::sock = '__hidden__';

$forum::num_queries = 0;
$forum::total_time = 0;

sub connect_to_database
{
#	$forum::errmsg = 'The forum is temporarily locked down for maintenance';
#	$forum::errno = -300;
#	return 0;
	
	$dbh = DBI->connect("DBI:mysql:$forum::db:$forum::host:" .
						"mysql_socket=$forum::sock",
						$forum::user, $forum::pass,
						{RaiseError=>0, PrintError=>1, AutoCommit=>1});
	if (!$dbh)
	{
		$forum::errmsg='Error('.DBI->err().'): Could not connect to database';
		$forum::errno = DBI->err();
	}
	else
	{
		$forum::errmsg = '';
		$forum::errno = 0;
	}
	return $dbh;
}

# Contract: none of the following functions will be called
# if the above function does not turn out well

sub forum_disconnect
{
	$dbh->disconnect();
}

use Carp ();
local $SIG{__WARN__} = \&Carp::cluck;

sub db_do
{
	$forum::num_queries++;
	my $start_time = time;
	my $sth = $dbh->prepare(shift);
	my $rv = $sth && $sth->execute(@_);
	print STDERR "\ncluck()\n";
	Carp::cluck();
	print STDERR "\nendcluck()\n";
	$forum::total_time += time() - $start_time;
	if (!$rv)
	{
		$forum::errmsg = 'Error('.$dbh->err().'):'.$dbh->errstr();
		$forum::errno = $dbh->err();
		return 0;
	}
	else
	{
		$forum::errmsg = '';
		$forum::errno = 0;
		return $sth;
	}
}
sub db_query
{
	my $sth = db_do(@_);
	return 0 if (!$sth);
	
	my @rows = ();
	while (my $row = $sth->fetchrow_hashref())
	{
		push @rows, $row;
	}
	return \@rows;
}
sub db_get
{
	# if 0 is a possible valid return value, then you must check
	# $forum::errmsg or $forum::errno to see if an error occured
	my $sth = db_do(@_);
	return $sth ? $sth->fetchrow() : ();
}

sub record_hit
{
	my $full_url = shift;
	my $referer = shift;
	my $ip_addr = shift;
	my $hostname = shift;
	
	return db_do('INSERT INTO WEBSITE_HITS(FULL_URL,REFERER,IP_ADDR,HOSTNAME)'.
				 'VALUES (?,?,?,?)', $full_url, $referer, $ip_addr, $hostname);
}

sub get_current_user
{
	$forum::errmsg = '';
	$forum::errno = 0;
	my $sess_id = shift;
	# don't need to check "if ($sess_id)" because if it's 0,
	# nothing will show up anyway. this will never be called
	# with an undefined value for $sess_id
	my $users = db_query($forum::q_get_current_user, $sess_id);
	if (!$users)
	{
		# write to error logs
		# forum::errmsg is already set
		return 0;
	}
	elsif (@$users) # if there was at least one user
	{
		return $users->[0];
	}
	else # means their session id "expired"
	{
		$forum::errmsg = 'Your session has expired.';
		$forum::errno = -2;
		return 0;
	}
}
sub find_user
{
	my ($username, $password) = @_;
	my $hidepass = '*' x length($password);
	my $users = db_query($forum::q_find_user, $username, $password);
	if (@$users) # this will never be > than 1
	{
		$forum::errmsg = '';
		$forum::errno = 0;
		return $users->[0];
	}
	else
	{
		$forum::errmsg = 'The username/password combination ' .
						 "'$username'/'$hidepass' was not found.";
		$forum::errno = -10;
		return 0;
	}
}
sub num_users
{
	return db_get('SELECT COUNT(*)FROM FM_USERS');
}
sub get_users
{
	my $page = shift;
	# page is perfect, because it was verified earlier
	my $offset = ($page - 1) * $forum::u_p;
	return db_query($forum::q_get_users, $offset, $forum::u_p);
}
sub get_user
{
	my $id = shift;
	my $users = db_query('SELECT * FROM FM_USERS WHERE ID=?', $id);
	if (!$users)
	{
		# $forum::errmsg is already set
		return 0;
	}
	elsif (@$users)
	{
		$forum::errmsg = '';
		$forum::errno = 0;
		return $users->[0];
	}
	else
	{
		$forum::errmsg = "User '$id' does not exist!";
		$forum::errno = -7;
		return 0;
	}
}


# in the future, if you ever want to block ipaddresses, do it here?
my $print_regex = qr/[\x20-\x7E]/;
my $email_regex =
qr/^(?>[a-z0-9_!#\$%\^&*\-=+.\/?{}|`~]+)@(?:(?>[a-z0-9\-]+)\.)+[a-z]{2,6}$/i;
sub verify_regist
{
	my ($username, $password, $password2, $email) = @_;
	
	my @errs = ();
	if (length($username) < 4 || length($username) > 24)
	{
		push @errs, 'Username must be between 4 and 24 characters';
	}
	if ($username =~ m/^\s|\s$/)
	{
		push @errs, 'Username must not begin or end with a space';
	}
	if ($username !~ $print_regex)
	{
		push @errs, '(Username:) ONLY PRINTABLE CHARACTERS';
	}
	if (!@errs && (my $usernm =
		db_get('SELECT USERNM FROM FM_USERS WHERE USERNM=?', $username)))
	{
		push @errs, "Username '$usernm' is already taken";
	}
	
	if (length($password) < 4 || length($password) > 24)
	{
		push @errs, 'Password must be between 4 and 24 characters';
	}
	if ($password !~ $print_regex)
	{
		push @errs, '(Password:) ONLY PRINTABLE CHARACTERS';
	}
	if ($password ne $password2)
	{
		push @errs, 'Passwords do not match';
	}
	
	if (length($email) > 128)
	{
		push @errs, 'Email address must be less than 128 characters';
	}
	elsif ($email !~ $email_regex)
	{
		push @errs, "'$email' is not a legal email address";
	}
	elsif (my $eml = db_get('SELECT EMAIL FROM FM_USERS WHERE EMAIL=?',$email))
	{
		push @errs, "Email address '$eml' is already taken";
	}
	
	return @errs;
}
sub create_user
{
	my ($username,$password,$password2,$email,$hidee,$ip,$hostname) = @_;
	if (my @errs = verify_regist($username,$password,$password2,$email))
	{
		$forum::errmsg =  join "\n", @errs;
		$forum::errno = -4;
		return 0;
	}
	
	$forum::errmsg = '';
	$forum::errno = 0;
	# yay we're all set to go
	if (!db_do($forum::d_create_user, $username, $password, $email, $hidee,
				$ip, $hostname))
	{
		return 0;
	}
	if (my $users = db_query('SELECT*FROM FM_USERS WHERE ID=LAST_INSERT_ID()'))
	{
		if (@$users)
		{
			$forum::errmsg = '';
			$forum::errno = 0;
			return $users->[0];
		}
		else
		{
			$forum::errmsg = 'It seems your registration didn\'t go through. Please try again, or contact the administrator.';
			$forum::errno = -11;
			return 0;
		}
	}
	else
	{
		# $forum::errmsg is already set
		return 0;
	}
}
sub create_sess_id
{
	my $user = shift;
	for (1 .. 100) # as opposed to "while (1)" to avoid an infinite loop
	{
		my $sess_id = '';
		for (1 .. 32)
		{
			my $rand = int(rand(36));
			if ($rand < 10)
			{
				$sess_id .= chr(ord('0') + $rand);
			}
			else
			{
				$sess_id .= chr(ord('a') + ($rand - 10));
			}
		}
		if (db_do($forum::d_set_sess_id, $sess_id, $user->{ID}))
		{
			$forum::errmsg = '';
			$forum::errno = 0;
			return $sess_id;
		}
		
		return 0 unless ($forum::errno == 1062); # 1062 = duplicate entry
	}
	$forum::errmsg = 'Could not create a session id that wasn\'t taken. Please try again';
	$forum::errno = -3;
	return 0;
}
sub clear_user_sess
{
	return db_do('UPDATE FM_USERS SET SESS_ID=NULL WHERE SESS_ID=?', shift);
}

sub verify_post
{
	my $content = shift;
	my @errs = ();
	
	if (length($content) < 1 || length($content) > 15000)
	{
		push @errs, 'Post must be between 1 and 15000 characters';
	}
	if ($content !~ m/\S/ && $content ne '')
	{
		push @errs, 'Post content can not be only spaces';
	}
	
	return @errs;
}
sub verify_topic
{
	my ($title,$descr,$content) = @_;
	
	my @errs = ();
	if (length($title) < 1 || length($title) > 64)
	{
		push @errs, 'Title must be between 1 and 64 characters';
	}
	if ($title =~ /^\s|\s$/)
	{
		push @errs, 'Title must not begin or end with a space';
	}
	
	if (length($descr) > 128)
	{
		push @errs, 'Description must be less than 128 characters';
	}
	if ($descr =~ /^\s|\s$/)
	{
		push @errs, 'Description can not begin or end with a space';
	}
	if ($descr !~ m/\S/ && $descr ne '')
	{
		push @errs, 'Description can not be only spaces';
	}
	
	push @errs, verify_post($content);
	
	return @errs;
}
sub make_topic
{
	my ($board,$user,$title,$descr,$content) = @_;
	$content =~ s/\r//g;
	if (my @errs = verify_topic($title, $descr, $content))
	{
		$forum::errmsg = join("\n", @errs);
		$forum::errno = -4;
		return 0;
	}
	
	$forum::errmsg = '';
	$forum::errno = 0;
	if (db_do($forum::d_make_topic,
			  $board->{ID}, $user->{ID}, $title, $descr, $content))
	{
		# $forum::errmsg and errno are all good
		return db_get('SELECT @TOPIC_ID, @POST_ID');
	}
	else
	{
		# $forum::errmsg is already set
		return 0;
	}
}
sub make_post
{
	my ($topic,$user,$content,$ip_addr,$hostname) = @_;
	$content =~ s/\r//g;
	if (my @errs = verify_post($content))
	{
		$forum::errmsg = join("\n", @errs);
		$forum::errno = -4;
		return 0;
	}
	
	$forum::errmsg = '';
	$forum::errno = 0;
	if (db_do($forum::d_make_post, $topic->{ID}, $user->{ID}, $content,
			  $ip_addr, $hostname))
	{
		# $forum::errmsg and errno are all good
		print STDERR "none what\n";
		return db_get('SELECT @POST_ID');
	}
	else
	{
		# $forum::errmsg is already set
		print STDERR 'error', "\n";
		return 0;
	}
}
sub edit_post
{
	my ($post,$user,$content) = @_; # so for like, "post was last edited by..."
	$content =~ s/\r//g;
	if (my @errs = verify_post($content))
	{
		$forum::errmsg = join("\n", @errs);
		$forum::errno = -4;
		return 0;
	}
	$forum::errmsg = '';
	$forum::errno = 0;
	return db_do($forum::d_edit_post, $content, $post->{ID});
}
sub edit_topic
{
	my ($topic,$user,$title,$descr,$content) = @_;
	$content =~ s/\r//g;
	if (my @errs = verify_topic($title, $descr, $content))
	{
		$forum::errmsg = join("\n", @errs);
		$forum::errno = -4;
		return 0;
	}
	$forum::errmsg = '';
	$forum::errno = 0;
	return	db_do($forum::d_edit_topic, $title, $descr, $topic->{ID}) &&
			db_do($forum::d_edit_post, $content, $topic->{FIRST_POST});
}
sub get_boards
{
	return db_query($forum::q_get_boards);
}
sub get_board
{
	my $id = shift;
	my $boards = db_query('SELECT * FROM FM_BOARDS WHERE ID=?', $id);
	if (!$boards)
	{
		# $forum::errmsg is already set
		return 0;
	}
	if (@$boards)
	{
		$forum::errmsg = '';
		$forum::errno = 0;
		return $boards->[0];
	}
	else
	{
		$forum::errmsg = "Board '$id' does not exist!";
		$forum::errno = -6;
		return 0;
	}
}
sub get_topic
{
	my $id = shift;
	my $topics = db_query($forum::q_get_topic, $id);
	if (!$topics)
	{
		# $forum::errmsg is already set
		return 0;
	}
	if (@$topics)
	{
		$forum::errmsg = '';
		$forum::errno = 0;
		return $topics->[0];
	}
	else
	{
		$forum::errmsg = "Topic '$id' does not exist!";
		$forum::errno = -6;
		return 0;
	}
}
sub get_topics
{
	# won't be called if board doesn't exist!
	my $board = shift;
	my $page = shift; # assume perfect input
	# however, here is where you check if $page > total number of pages
	my $offset = ($page - 1) * $forum::t_p;
	
	return db_query($forum::q_get_topics,$board->{ID},$offset,$forum::t_p);
}
sub get_post
{
	my $id = shift;
	my $posts = db_query($forum::q_get_post, $id);
	if (!$posts)
	{
		# $forum::errmsg is already set
		return 0;
	}
	if (@$posts)
	{
		$forum::errmsg = '';
		$fourm::errno = 0;
		return $posts->[0];
	}
	else
	{
		$forum::errmsg = "Post '$id' does not exist!";
		$forum::errno = -6;
		return 0;
	}
}
sub get_posts
{
	my $topic = shift;
	my $page = shift;
	# however, here is where you check if $page > total number of pages
	my $offset = ($page - 1) * $forum::p_p;
	
	return db_query($forum::q_get_posts,$topic->{ID},$offset,$forum::p_p);
}




sub cant_makepost
{
	my ($user, $topic) = @_;
	if (!$user)
	{
		return 'You must be logged in to post';
	}
	return '';
}
sub cant_maketopic
{
	my ($user, $board) = @_;
	return $user ? '' : 'You must be logged in to create a topic';
}
sub cant_editpost
{
	my ($user, $post) = @_;
	return $user && ($user->{ID} eq $post->{POSTER} || $user->{PRIV} eq 'A') ?
			'' :
			'You must be the author of this post, or the admin to edit it';
}
sub cant_edittopic
{
	my ($user, $topic) = @_;
	return $user && ($user->{ID} eq $topic->{STARTED} || $user->{PRIV} eq 'A')?
			'' :
			'You must be the topic creator or the admin to edit it.';
}

1;
